<?php
class Mozg_Source_Table_Fields
{
	protected $_tableFields = array();
	protected $_fieldsMap = array();
	protected $_multilangFields = array();
	protected $_source;

	private $_delimiter = ':';

	public function __construct(array $fields, array $fieldsMap, $source)
	{
		$this->_source = $source;
		$this->_tableFields = $fields;

		$fieldKeys = array_keys($fields);
		$this->_fieldsMap = array_combine($fieldKeys, $fieldKeys);

		foreach ($fieldsMap as $fieldName => $map)
		{
			//if (!isset($this->_fieldsMap[$fieldName])) continue;

			if (isset($map['multilang']) && $map['multilang'])
			{
				$langCount = 0;
				foreach (Mozg_Lang::$langs as $lang)
				{
					$langField = $fieldName. '_' .$lang;
					if (isset($this->_fieldsMap[$langField]))
					{
						$langCount++;
						unset($this->_fieldsMap[$langField]);
					}
				}

				if ($langCount == sizeof(Mozg_Lang::$langs))
				{
					$this->_fieldsMap[$fieldName] = $fieldName;
					$this->_multilangFields[$fieldName] = true;
				}
				else
				{
					$map['map'] = null;
				}
			}

			if (isset($map['map']))
			{
				$this->_fieldsMap[$fieldName] = $map['map'];
			}
		}
	}

	protected function FilterFields(array $fields, array $allFields)
	{
		if (!sizeof($fields))
		{
			$fields = $allFields;
		}
		else
		{
			if (sizeof(array_unique($fields)) > 1)
			{
				throw new Mozg_Exception_System('You cannot currently mix including and excluding fields');
			}

			//fix unknown fields
			$fields = array_intersect_key($fields, $allFields);

			// including fields
			if (current($fields))
			{
				$fields = array_intersect_key($allFields, $fields);
			}
			//excluding fields
			else
			{
				$fields = array_diff_key($allFields, $fields);
			}
		}

		return $fields;
	}

	protected function GetLangsByCode($getLangsCode = 0)
	{
		$getLangs = array();
		switch ($getLangsCode)
		{
			case -1:
				$getLangs = Mozg_Lang::$langs;
				$pos = array_search(Mozg_Lang::$active, $getLangs);
				if ($pos !== false)
				{
					unset($getLangs[$pos]);
				}
				break;

			case 0:
				$getLangs = array(Mozg_Lang::$active);
				break;

			case 1:
				$getLangs = Mozg_Lang::$langs;
				break;

			default:
				throw new Mozg_Exception_System('Invalid lang code');
		}

		return $getLangs;
	}

	public function GetSqlMapList(array $fields = null, $tablePrefix = '', $fieldsPrefix = '', $getLangsCode = 0)
	{
		if ($fields === null) $fields = array();

		$fields = $this->FilterFields($fields, $this->_fieldsMap);

		if (strlen($tablePrefix)) $tablePrefix .= '.';
		if (strlen($fieldsPrefix)) $fieldsPrefix .= $this->_delimiter;

		$getLangs = $this->GetLangsByCode($getLangsCode);

		$fieldsList = array();
		foreach ($fields as $dbField => $k)
		{
			$mapField = $this->_fieldsMap[$dbField];

			$langs = array('');
			if (isset($this->_multilangFields[$dbField]))
			{
				$langs = $getLangs;
			}

			foreach ($langs as $lang)
			{
				if (strlen($lang))
				{
					$fLang = '_'. $lang;
					$oLang = $this->_delimiter .$lang;
				}
				else
				{
					$fLang = $oLang = '';
				}

				$fieldsList[] = $tablePrefix. '`' .$dbField.$fLang. '`' .(($dbField != $mapField) || strlen($fieldsPrefix) || strlen($oLang) ? ' `' .$fieldsPrefix.$mapField.$oLang. '`' : '');
			}
		}

		return $fieldsList;
	}

	public function GetSqlMap(array $fields = null, $tablePrefix = '', $fieldsPrefix = '', $langs = 0)
	{
		return implode(',', $this->GetSqlMapList($fields, $tablePrefix, $fieldsPrefix, $langs));
	}

	public function GetFieldByMap($fieldName)
	{
		return array_search($fieldName, $this->_fieldsMap);
	}

	public function GetFreeFields(Mozg_Property_Params $obj, array $fields = null)
	{
		$freeFields = array_diff($this->_fieldsMap, array_keys($obj->ExportData()));
		$freeFields = array_fill_keys(array_keys($freeFields), 1);

		return $this->FilterFields($fields, $freeFields);
	}

	public function GetSqlValues(Mozg_Property_Params $obj, $onlyChanged = true, $tablePrefix = '', array $fields = null, array $addFields = array())
	{
		if ($fields === null) $fields = array();

		$fields = $this->FilterFields($fields, $this->_fieldsMap);

		if (strlen($tablePrefix)) $tablePrefix .= '.';

		$origObj = $obj->Orig();

		$fieldsList = array();
		foreach ($fields as $dbField => $k)
		{
			$mapField = $this->_fieldsMap[$dbField];

			$langs = array('');
			if (isset($this->_multilangFields[$dbField]))
			{
				$langs = Mozg_Lang::$langs;
			}

			foreach ($langs as $lang)
			{
				if (strlen($lang))
				{
					$fLang = '_'. $lang;
				}
				else
				{
					$fLang = '';
				}

				if (isset($addFields[$dbField.$fLang]))
				{
					$value = $addFields[$dbField.$fLang];
				}
				else
				{
					$value = $obj->{'Get' .$mapField}($lang);
				}

				if (
						$onlyChanged
						&&
						!($value instanceof Mozg_Property_Params)
						&&
						($value === $origObj->{'Get' .$mapField}($lang))
					)
				{
					continue;
				}

				$fieldsList[$tablePrefix. '`' .$dbField.$fLang. '`'] =  $this->PrepareValue($dbField.$fLang, $value, $obj->DefaultValue($mapField, $lang), $obj->Config()->GetFieldType($mapField));
			}
		}

		return $fieldsList;
	}

	public function GetSqlUpdate(Mozg_Property_Params $obj, $tablePrefix = '', array $fields = null, array $addFields = array())
	{
		$fieldsList = $this->GetSqlValues($obj, true, $tablePrefix, $fields, $addFields);

		$stmt = array();
		foreach ($fieldsList as $fieldTitle => $fieldValue)
		{
			$stmt[] = $fieldTitle. '=' .$fieldValue;
		}

		return implode(',', $stmt);
	}

	public function GetSqlInsert(Mozg_Property_Params $obj, $tablePrefix = '', array $fields = null, array $addFields = array())
	{
		$fieldsList = $this->GetSqlValues($obj, false, $tablePrefix, $fields, $addFields);

		return array(
			'fields' => implode(",", array_keys($fieldsList)),
			'values' => implode(",", array_values($fieldsList)),
		);
	}

	protected function PrepareValue($dbFieldName, $value, $defaultValue, $type)
	{
		if (!isset($this->_tableFields[$dbFieldName])) throw new Mozg_Exception_System('Field ' .$dbFieldName. ' not found');

		$fieldConfig = $this->_tableFields[$dbFieldName];

		if (
				(
					is_null($value)
					||
					(
						!($value instanceof Mozg_Property_Params)
						&&
						($value === $defaultValue)
					)
					||
					(
						($value instanceof Mozg_Property_Params)
						&&
						($value === $defaultValue)
						&&
						($value->ExportData() === $value->Orig()->ExportData())
					)
					||
					(
						($value instanceof Mozg_Property_Params)
						&&
						($value !== $defaultValue)
						&&
						($value->ExportData() === $defaultValue->Orig()->ExportData())
					)
				)
				&&
				$fieldConfig['null']
			)
		{
			return "NULL";
		}

		switch ($fieldConfig['type'])
		{
			case 'char':
			case 'varchar':
			case 'text':
			case 'mediumtext':
			case 'longtext':
			case 'binary':
			case 'varbinary':
			case 'tinyblob':
			case 'mediumblob':
			case 'blob':
			case 'longblob':
				switch ($type)
				{
					case 'hash':
						$value = Mozg::Lib('Json')->Encode($value, JSON_FORCE_UTF);
						break;

					case 'array':
						$value = implode(",", $value);
						break;

					case 'entity':
						$value = (string)$value;
						break;

					default:
						$value = (string)$value;
				}

				$length = (int)$fieldConfig['length'];
				if ($length)
				{
					$value = Mozg::Lib('Str')->SubStr($value, 0, $length);
				}

				$value = $this->_source->quote($value);

				break;

			case 'tinyint':
			case 'smallint':
			case 'mediumint':
			case 'int':
			case 'integer':
			case 'bigint':
			case 'serial'://synonym BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE
			case 'boolean': //synonym TINYINT(1)
			case 'bool':
				$value = (int)$value;
				break;

			case 'float':
				if (is_object($value)) $value = (int)$value;
				else $value = (float)$value;
				break;

			case 'double':
				if (is_object($value)) $value = (int)$value;
				else $value = (double)$value;
				break;

			case 'real':
			case 'double precision':
				if (is_object($value)) $value = (int)$value;
				else $value = (real)$value;
				break;

			case 'decimal':
			case 'dec':
			case 'numeric':
			case 'fixed':
				if (is_object($value)) $value = (int)$value;
				else
				{
					$value = (float)$value;
				}

				break;

			case "date":
			case "datetime":
			case "timestamp":
			case "year":
				if (!is_numeric($value))
				{
					$value = strtotime((string)$value);
				}

				$value = Mozg::Lib('date')->Format((int)$value, 'Y-m-d H:i:s');
				$value = $this->_source->quote($value);

				break;

			case "enum":
				$value = (string)$value;
				$enum = explode("','", trim($fieldConfig['default'], "'"));
				if (!in_array($value, $enum))
				{
					$value = (int)$fieldConfig['default'];
				}

				if (!is_int($value)) $value = $this->_source->quote($value);

				break;

			case "set":
				$set = explode("','", trim($fieldConfig['default'], "'"));

				if (is_string($value))
				{
					$value = explode(",", $value);
				}
				else
				{
					$value = (array)$value;
				}

				$value = array_intersect($value, $set);

				if (!sizeof($value))
				{
					$value = (int)$fieldConfig['default'];
				}

				if (is_array($value))
				{
					$value = $this->_source->quote(implode(',', $value));
				}

				break;

			default:

				$value = $this->_source->quote((string)$value);

		}

		return $value;
	}
}